using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using LoD.Contracts;
using LoD.Contracts.MapEngine;
using LoD.WPClientLibrary.Interfaces;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;


namespace LoD.WPClientLibrary.MapEngine {
    [DataContract, KnownType(typeof(MapCell))]
    
    public class MapRow : IMapRow {
        public MapRow() { this.Columns = new List<IMapCell>(); }

        #region IMapRow Members

        [DataMember]
        public List<IMapCell> Columns { get; set; }

        #endregion
    }

    [DataContract, KnownType(typeof(MapRow))]
    
    public class TileMap : ITileMap {
        private readonly Texture2D _mouseMap;
        private readonly Texture2D _slopeMaps;
        private readonly TileSet _tileSet;
        private int _mapHeight;
        private int _mapWidth;

        public TileMap() { }

        public TileMap(Texture2D mouseMap, Texture2D slopeMap, TileSet tileSet, int mapHeight, int mapWidth) {
            this._mouseMap = mouseMap;
            this._slopeMaps = slopeMap;
            this._tileSet = tileSet;
            this.MapHeight = mapHeight;
            this.MapWidth = mapWidth;
            this.Rows = new List<IMapRow>();

            for (int y = 0; y < this.MapHeight; y++) {
                var thisRow = new MapRow();
                for (int x = 0; x < this.MapWidth; x++) thisRow.Columns.Add(new MapCell(0));
                this.Rows.Add(thisRow);
            }
        }

        #region ITileMap Members

        [DataMember]
        public int MapHeight {
            get { return this._mapHeight; }
            set {
                if (Rows == null) Rows = new List<IMapRow>();
                this.Rows.Clear();
                for (int y = 0; y < this.MapHeight; y++) {
                    var thisRow = new MapRow();
                    for (int x = 0; x < this.MapWidth; x++) thisRow.Columns.Add(new MapCell(0));
                    this.Rows.Add(thisRow);
                }
                this._mapHeight = value;
            }
        }

        [DataMember]
        public int MapWidth {
            get { return this._mapWidth; }
            set {
                if (Rows == null) Rows = new List<IMapRow>();
                this.Rows.Clear();
                for (int y = 0; y < this.MapHeight; y++) {
                    var thisRow = new MapRow();
                    for (int x = 0; x < this.MapWidth; x++) thisRow.Columns.Add(new MapCell(0));
                    this.Rows.Add(thisRow);
                }
                this._mapWidth = value;
            }
        }

        [DataMember]
        public List<IMapRow> Rows { get; set; }

        public Point WorldToMapCell(Point worldPoint, out Point localPoint) {
            var mapCell = new Point(
                (worldPoint.X / this._mouseMap.Width),
                ((worldPoint.Y / this._mouseMap.Height)) * 2
                );

            int localPointX = worldPoint.X % this._mouseMap.Width;
            int localPointY = worldPoint.Y % this._mouseMap.Height;

            int dx = 0;
            int dy = 0;

            var myUint = new uint[1];

            if (new Rectangle(0, 0, this._mouseMap.Width, this._mouseMap.Height).Contains(localPointX, localPointY)) {
                this._mouseMap.GetData(0, new Rectangle(localPointX, localPointY, 1, 1), myUint, 0, 1);

                if (myUint[0] == 0xFF0000FF) { // Red
                    dx = -1;
                    dy = -1;
                    localPointX = localPointX + (this._mouseMap.Width / 2);
                    localPointY = localPointY + (this._mouseMap.Height / 2);
                }

                if (myUint[0] == 0xFF00FF00) { // Green
                    dx = -1;
                    localPointX = localPointX + (this._mouseMap.Width / 2);
                    dy = 1;
                    localPointY = localPointY - (this._mouseMap.Height / 2);
                }

                if (myUint[0] == 0xFF00FFFF) { // Yellow
                    dy = -1;
                    localPointX = localPointX - (this._mouseMap.Width / 2);
                    localPointY = localPointY + (this._mouseMap.Height / 2);
                }

                if (myUint[0] == 0xFFFF0000) { // Blue
                    dy = +1;
                    localPointX = localPointX - (this._mouseMap.Width / 2);
                    localPointY = localPointY - (this._mouseMap.Height / 2);
                }
            }

            mapCell.X += dx;
            mapCell.Y += dy - 2;

            localPoint = new Point(localPointX, localPointY);

            return mapCell;
        }

        public Point WorldToMapCell(Point worldPoint) {
            Point dummy;
            return this.WorldToMapCell(worldPoint, out dummy);
        }

        public Point WorldToMapCell(Vector2 worldPoint) { return this.WorldToMapCell(new Point((int)worldPoint.X, (int)worldPoint.Y)); }

        public IMapCell GetCellAtWorldPoint(Point worldPoint) {
            Point mapPoint = this.WorldToMapCell(worldPoint);
            return this.Rows[mapPoint.Y].Columns[mapPoint.X];
        }

        public IMapCell GetCellAtWorldPoint(Vector2 worldPoint) { return this.GetCellAtWorldPoint(new Point((int)worldPoint.X, (int)worldPoint.Y)); }

        public int GetSlopeMapHeight(Point localPixel, TileSlope slopeTile) {
            var texturePoint = new Point((int)slopeTile * this._mouseMap.Width + localPixel.X, localPixel.Y);

            var slopeColor = new Color[1];

            if (new Rectangle(0, 0, this._slopeMaps.Width, this._slopeMaps.Height).Contains(texturePoint.X,
                                                                                            texturePoint.Y)) {
                this._slopeMaps.GetData(0, new Rectangle(texturePoint.X, texturePoint.Y, 1, 1), slopeColor, 0, 1);

                var offset = (int)(((255 - slopeColor[0].R) / 255f) * this._tileSet.HeightTileOffset);

                return offset;
            }

            return 0;
        }

        public int GetSlopeHeightAtWorldPoint(Point worldPoint) {
            Point localPoint;
            Point mapPoint = this.WorldToMapCell(worldPoint, out localPoint);
            TileSlope slopeTile = this.Rows[mapPoint.Y].Columns[mapPoint.X].SlopeTile;

            return this.GetSlopeMapHeight(localPoint, slopeTile);
        }

        public int GetSlopeHeightAtWorldPoint(Vector2 worldPoint) { return this.GetSlopeHeightAtWorldPoint(new Point((int)worldPoint.X, (int)worldPoint.Y)); }

        public int GetOverallHeight(Point worldPoint) {
            Point mapCellPoint = this.WorldToMapCell(worldPoint);
            int height = this.Rows[mapCellPoint.Y].Columns[mapCellPoint.X].StackTiles.Count
                         * this._tileSet.HeightTileOffset;
            height += this.GetSlopeHeightAtWorldPoint(worldPoint);

            return height;
        }

        public int GetOverallHeight(Vector2 worldPoint) { return this.GetOverallHeight(new Point((int)worldPoint.X, (int)worldPoint.Y)); }

        #endregion

        public void Import(Stream data) {
            var serializer = new DataContractJsonSerializer(typeof(TileMap));
            var tmp = serializer.ReadObject(data);
            var importMap = tmp as ITileMap;
  
            if (importMap == null) return;
            this.MapWidth = importMap.MapWidth;
            this.MapHeight = importMap.MapHeight;
            this.Rows = importMap.Rows;
        }
    }
}